#!/usr/bin/env bun

import fs from 'fs/promises';
import path from 'path';
import { parseArgs } from 'util';

type BuildArtifacts = Awaited<ReturnType<typeof Bun.build>>['outputs'];
interface Entrypoint {
  name: string;
  path: string;
}
interface EntrypointOutput extends Entrypoint {
  script: string;
}

type Platform = 'android' | 'ios';

const packageRoot = path.resolve(__dirname, '..');
const browserScriptsRoot = path.join(packageRoot, 'browserScripts');
const outputPathAndroid = path.join(
  packageRoot,
  'android',
  'src/main/java/expo/modules/webview/DomWebViewBrowserScripts.kt'
);
const outputPathIos = path.join(packageRoot, 'ios', 'DomWebViewBrowserScripts.swift');

const ENTRYPOINTS: Entrypoint[] = [
  {
    name: 'INSTALL_GLOBALS_SCRIPT',
    path: path.join(browserScriptsRoot, 'InstallGlobals', 'index.ts'),
  },
  {
    name: 'NATIVE_EVAL_WRAPPER_SCRIPT',
    path: path.join(browserScriptsRoot, 'NativeEvalWrapper', 'index.ts'),
  },
];

const { values: args } = parseArgs({
  args: Bun.argv.slice(2),
  options: {
    h: {
      type: 'boolean',
    },
    platform: {
      type: 'string',
    },
  },
  strict: true,
  allowPositionals: false,
});

async function runAsync() {
  if (args.h) {
    console.log(`Usage: ${path.basename(__filename)} --platform <all|android|ios>`);
    return;
  }

  const platform = args.platform ?? 'all';
  if (platform === 'android' || platform === 'all') {
    await createAndroidFileAsync();
  }
  if (platform === 'ios' || platform === 'all') {
    await createIosFileAsync();
  }
}

runAsync().catch((error) => {
  console.error(error);
  process.exit(1);
});

//#region Internals

async function createAndroidFileAsync() {
  const scripts: string[] = [];
  const entrypointOutputs = await Promise.all(
    ENTRYPOINTS.map((entrypoint) => buildEntryPointAsync({ entrypoint, platform: 'android' }))
  );
  for (const entrypointOutput of entrypointOutputs) {
    scripts.push(
      `internal const val ${entrypointOutput.name}: String = """\n${entrypointOutput.script}\n"""`
    );
  }

  const contents = `\
// Copyright 2015-present 650 Industries. All rights reserved.

package expo.modules.webview

/**
 * This file contains the browser scripts that are injected into the WebView.
 * @generated by buildBrowserScripts.ts
 */

${scripts.join('\n\n')}
`;

  console.log(`Generating browser scripts for Android at ${outputPathAndroid}`);
  await fs.writeFile(outputPathAndroid, contents);
}

async function createIosFileAsync() {
  const scripts: string[] = [];
  const entrypointOutputs = await Promise.all(
    ENTRYPOINTS.map((entrypoint) => buildEntryPointAsync({ entrypoint, platform: 'ios' }))
  );
  for (const entrypointOutput of entrypointOutputs) {
    scripts.push(
      `internal let ${entrypointOutput.name}: String = """\n${entrypointOutput.script}\n"""`
    );
  }

  const contents = `\
// Copyright 2015-present 650 Industries. All rights reserved.

// swiftlint:disable line_length

/**
 * This file contains the browser scripts that are injected into the WebView.
 * @generated by buildBrowserScripts.ts
 */

${scripts.join('\n\n')}

// swiftlint:enable line_length
`;

  console.log(`Generating browser scripts for iOS at ${outputPathIos}`);
  await fs.writeFile(outputPathIos, contents);
}

async function buildEntryPointAsync({
  entrypoint,
  platform,
}: {
  entrypoint: Entrypoint;
  platform: Platform;
}): Promise<EntrypointOutput> {
  const buildOutput = await Bun.build({
    entrypoints: [entrypoint.path],
    target: 'browser',
  });
  if (!buildOutput.success) {
    const message = buildOutput.logs.map((log) => log.message).join('\n');
    throw new Error(`Failed to build browser scripts: ${message}`);
  }
  const script = await createMergedScriptAsync(buildOutput.outputs, platform);
  return { ...entrypoint, script };
}

async function createMergedScriptAsync(
  outputs: BuildArtifacts,
  platform: Platform
): Promise<string> {
  const mergedScript = await Promise.all(outputs.map((output) => output.text()));
  let result = mergedScript.join('\n');

  if (platform === 'android') {
    result = result
      // Replace `foo.${var}` to `foo.${'$'}{var}` to avoid Kotlin string interpolation.
      .replace(/\$(\{\w)/g, "$${'$$'}$1")
      // Bun bundler will replace `foo.$$` to `foo.\$\$` in the output, which is not what we want.
      .replace(/\\\$\\\$/g, '$$$$')
      // Replace `$$` to `${'$'}${'$'}` to avoid Kotlin string interpolation.
      .replace(/\$\$/g, "$${'$$'}$${'$$'}");
  } else if (platform === 'ios') {
    // Bun bundler will replace `foo.$$` to `foo.\$\$` in the output, which is not what we want.
    result = result.replace(/\\\$\\\$/g, '$$$$');
  }
  return result;
}

//#endregion Internals
